#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Privacy for attributes fetched from class instances.
See self-test code at end of file for a usage example.
Decorator same as: Doubler = Private('data', 'size')(Doubler).
Private returns onDecorator, onDecorator returns onInstance,
and each onInstance instance embeds a Doubler instance.
"""


def Private(*privates, trace=True):
    def onDecorator(cls):
        class onInstance:
            def __init__(self, *args, **kwargs):
                self.wrapped = cls(*args, **kwargs)

            def __getattr__(self, attr):
                if trace:
                    print('[{0}]'.format(' '.join(map(str, ['get:', attr]))))
                    if attr in privates:
                        raise TypeError('private attribute fetch: {0}'.format(attr))
                return getattr(self.wrapped, attr)

            def __setattr__(self, attr, value):
                if trace:
                    print('[{0}]'.format(' '.join(map(str, ['set:', attr, value]))))
                    if attr == 'wrapped':
                        self.__dict__[attr] = value
                    elif attr in privates:
                        raise TypeError('private attribute change: {0}'.format(attr))
                self.__dict__[attr] = value

        return onInstance
    return onDecorator


if __name__ == '__main__':
    @Private('data', 'size', trace=True)
    class Doubler:
        def __init__(self, label, start):
            self.label = label
            self.data = start

        def size(self):
            return len(self.data)

        def double(self):
            for i in range(self.size()):
                self.data[i] = self.data[i] * 2

        def display(self):
            print('{0} => {1}'.format(self.label, self.data))

    X = Doubler('X is', [1, 2, 3])
    Y = Doubler('Y is', [-10, -20, -30])
    print('')
    # succeed
    print(X.label)
    X.display(); X.double(); X.display()
    print(Y.label)
    Y.display(); Y.double(); Y.display()
    Y.label = 'Spam'
    Y.display()
    print('')
    # fail
    try:
        print(X.size())
    except TypeError as e:
        print(e, end='\n')
    try:
        print(X.data)
    except TypeError as e:
        print(e, end='\n')
    try:
        print(X.data)
    except TypeError as e:
        print(e, end='\n')
    try:
        X.data = [1, 1, 1]
    except TypeError as e:
        print(e, end='\n')
    try:
        X.size = lambda S: 0
    except TypeError as e:
        print(e, end='\n')
    try:
        print(Y.data)
    except TypeError as e:
        print(e, end='\n')
    try:
        print(Y.size())
    except TypeError as e:
        print(e, end='\n')
